package com.github.kittinunf.fuel.test;

import com.github.kittinunf.fuel.core.Headers;

import org.json.JSONObject;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.matchers.Times;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
import org.mockserver.model.HttpTemplate;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * MockHelper
 *
 * @since 2021-05-29
 */
public class MockHelper {
    private ClientAndServer mockServer;

    /**
     * setup
     */
    public void setup() {
        this.mockServer = ClientAndServer.startClientAndServer();
    }

    /**
     * tearDown
     */
    public void tearDown() {
        this.mockServer.stop();
    }

    /**
     * server
     *
     * @return ClientAndServer
     */
    public ClientAndServer server() {
        return this.mockServer;
    }

    /**
     * chain
     *
     * @param request request
     * @param response response
     */
    public void chain(HttpRequest request, HttpResponse response) {
        Times times = Times.once();
        ClientAndServer server = server();
        server.when(request, times).respond(response);
    }

    /**
     * chain
     *
     * @param request request
     * @param response response
     */
    public void chain(HttpRequest request, HttpTemplate response) {
        Times times = Times.once();
        ClientAndServer server = server();
        server.when(request, times).respond(response);
    }

    /**
     * request
     *
     * @return HttpResponse
     */
    public HttpRequest request() {
        return HttpRequest.request();
    }

    /**
     * response
     *
     * @return HttpResponse
     */
    public HttpResponse response() {
        return HttpResponse.response();
    }

    /**
     * responseTemplate
     *
     * @return HttpTemplate
     */
    public HttpTemplate responseTemplate() {
        return HttpTemplate.template(HttpTemplate.TemplateType.JAVASCRIPT);
    }

    /**
     * reflect
     *
     * @return HttpTemplate
     */
    public HttpTemplate reflect() {
        return responseTemplate().withTemplate(REFLECT_TEMPLATE);
    }


    /**
     * path
     *
     * @param path path
     * @return String
     * @throws MalformedURLException
     */
    public String path(String path) throws MalformedURLException {
        return new URL("http://localhost:" + server().getLocalPort() + "/" + path).toString();
    }

    final String REFLECT_TEMPLATE =
        "  return { " +
            "      'statusCode': 200, " +
            "  'headers': { " +
            "     'Date' : [ Date() ], " +
            "     'Content-Type' : [ 'application/json' ],  " +
            "     'Cookie' : request.headers['cookie'] || [] " +
            "  }, " +
            "  'body': JSON.stringify( " +
            "      {  " +
            "         method: request.method, " +
            "          path: request.path, " +
            "          query: request.queryStringParameters, " +
            "          body: request.body, " +
            "      headers: request.headers, " +
            "      reflect: true,  " +
            "      userAgent: (request.headers['user-agent'] " +
            "|| request.headers['User-Agent'] || [])[0]                    } " +
            "  ) " +
            "  }; ;";

    class MockReflected {
        String method;
        String path;
        List query;
        MockReflectedBody body = null;
        Headers headers = new Headers();
        boolean reflect = true;
        String userAgent = null;

        MockReflected() {
        }

        MockReflected(String method, String path) {
            this.method = method;
            this.path = path;
        }

        /**
         * from
         *
         * @param json json
         * @return MockReflected MockReflected
         */
        public MockReflected from(JSONObject json) {
            method = json.getString("method");
            path = json.getString("path");
            MockReflected base = new MockReflected(method, path);

            Set set = json.keySet();
            Iterable iterable = (Iterable) set;
            Iterator iterator = iterable.iterator();
            while (iterator.hasNext()) {
                String key = (String) iterator.next();
                switch (key) {
                    case "body":
                        MockReflectedBody mockReflectedBody = new MockReflectedBody();
                        JSONObject jsonObject = json.optJSONObject(key);
                        if (jsonObject == null) {
                            jsonObject = (new JSONObject()).put("type", "STRING").put("string", json.getString(key));
                        }
                        base.body = mockReflectedBody.from(jsonObject);
                        base.reflect = false;
                        continue;
                    case "userAgent":
                        base.userAgent = json.getString("userAgent");
                        continue;
                    case "reflect":
                        base.reflect = json.getBoolean(key);
                        continue;
                    case "headers":
                        Map map = json.getJSONObject(key).toMap();
                        base.headers = Headers.Companion.from(map);
                        continue;
                    case "query":
                        base.query = getList(json,key);
                        continue;
                    default:
                        break;
                }
            }
            return base;
        }
    }

    class MockReflectedBody {
        String type;
        String string = null;
        byte[] binary = null;
        String contentType = null;

        MockReflectedBody() {
        }

        MockReflectedBody(String type, String contentType) {
            this.type = type;
            this.contentType = contentType;
        }

        MockReflectedBody(String type, String string, byte[] binary, String contentType) {
            this.type = type;
            this.string = string;
            this.binary = binary;
            this.contentType = contentType;
        }

        /**
         * from
         *
         * @param json json
         * @return MockReflectedBody MockReflectedBody
         */
        public MockReflectedBody from(JSONObject json) {
            type = json.getString("type");
            contentType = json.getString("contentType");
            MockReflectedBody base = new MockReflectedBody(type, contentType);

            switch (base.type) {
                case "STRING":
                    base.string = json.getString("string");
                    break;
                case "BINARY":
                    base.binary = Base64.getDecoder().decode(json.getString("binary"));
                    break;
                default:
                    break;
            }
            return base;
        }

    }

    private List getList(JSONObject json,String key){
        JSONObject queryObject = json.getJSONObject(key);
        Set querySet = queryObject.keySet();
        Iterable queryIterable = (Iterable) querySet;
        Iterator queryIterator = queryIterable.iterator();
        List list = new ArrayList();
        while (queryIterator.hasNext()) {
            String parameter = (String) queryIterator.next();
            if (queryObject.isNull(parameter)) {
                list.add(parameter);
            } else {
                Object values = queryObject.get(parameter);
                list.add(values.toString());
            }
        }
        return list;
    }

}
